import { cloneDeep } from 'lodash-es'
import { UNIQUE_VALUE } from './const'
import { PromiseReject, PromiseResolve, PromiseValue } from '../types'

export type SingleThreadFn<T = any> = (...args: any[]) => Promise<T>

type SingleThread = {
  /** 结束后销毁 */
  destroy?: boolean
  /** 被执行函数的返回值 */
  data: any
  /** Promise */
  promise: [PromiseResolve, PromiseReject][]
}

type SingleThreadPool = Map<string, SingleThread>

const SINGLE_THREAD_POOLS = new WeakMap<SingleThreadFn, SingleThreadPool>()

const SINGLE_THREAD_PATH = new WeakMap<SingleThread, [fn: SingleThreadFn, key: string]>()

/**
 * 清除缓存
 * @param fn 被执行的异步函数
 * @param args 被执行函数的参数集合
 */
export function clearSingleThreadPromise<Fn extends SingleThreadFn>(fn: Fn, ...args: Parameters<Fn>) {
  const pool = SINGLE_THREAD_POOLS.get(fn)

  if (!pool) return

  const key = JSON.stringify(args)
  const thread = pool.get(key)

  if (!thread) return

  if (thread.promise.length) {
    thread.destroy = true
  } else {
    pool.delete(key)
  }

  if (!pool.size) SINGLE_THREAD_POOLS.delete(fn)
}

/**
 * 单线程的执行指定函数
 * @param fn 被执行的异步函数
 * @param args 被执行函数的参数集合
 */
export function runSingleThreadPromise<Fn extends SingleThreadFn>(fn: Fn, ...args: Parameters<Fn>) {
  const thread = createThread(fn, args)
  return execute(fn, args, thread)
}

/**
 * 根据被执行函数的当前传参生成对应的线程
 * @param fn 被执行的异步函数
 * @param args 被执行函数的参数集合
 */
function createThread<Fn extends SingleThreadFn>(fn: Fn, args: Parameters<Fn>) {
  let pool = SINGLE_THREAD_POOLS.get(fn)

  if (!pool) SINGLE_THREAD_POOLS.set(fn, (pool = new Map()))

  const key = JSON.stringify(args)

  let thread = pool.get(key)

  if (!thread) {
    pool.set(key, (thread = { data: UNIQUE_VALUE, promise: [] }))
    SINGLE_THREAD_PATH.set(thread, [fn, key])
  }

  return thread
}

/**
 * 根据线程实例删除对应的缓存
 * @param thread 线程实例
 * @param forcible 强制删除
 */
function removeThread(thread: SingleThread, forcible = false) {
  // 存在 且 （强制删除 或 标记了删除）
  if (SINGLE_THREAD_PATH.has(thread) && (forcible || thread.destroy)) {
    const [fn, key] = SINGLE_THREAD_PATH.get(thread)!
    const pool = SINGLE_THREAD_POOLS.get(fn)!

    pool.delete(key)
    if (!pool.size) SINGLE_THREAD_POOLS.delete(fn)
  }
}

/**
 * 执行函数
 * @param fn 被执行的异步函数
 * @param args 被执行函数的参数集合
 * @param thread 线程实例
 */
function execute<Fn extends SingleThreadFn>(fn: Fn, args: Parameters<Fn>, thread: SingleThread) {
  return new Promise<PromiseValue<ReturnType<Fn>>>((resolve, reject) => {
    try {
      if (thread.data === UNIQUE_VALUE) throw new Error()

      resolve(cloneDeep(thread.data))
      removeThread(thread)
    } catch (e) {
      const { promise } = thread

      if (!promise.length) executeFn(fn, args, thread)

      promise.push([resolve, reject])
    }
  })
}

/**
 * 执行函数
 * @param fn 被执行的异步函数
 * @param args 被执行函数的参数集合
 * @param thread 线程实例
 */
function executeFn<Fn extends SingleThreadFn>(fn: Fn, args: Parameters<Fn>, thread: SingleThread) {
  const { promise } = thread
  fn(...args)
    .then(
      (response) => {
        thread.data = response
        while (promise.length) {
          promise.shift()![0](cloneDeep(response))
        }
      },
      (e) => {
        while (promise.length) {
          promise.shift()![1](e)
        }
      }
    )
    .finally(() => removeThread(thread))
}
